package it.polito.let.jhp.types;

import java.io.Serializable;

import it.polito.let.jhp.SemanticException;

public class ComplexType implements JHPType, Serializable {

	private static final long serialVersionUID = 6904481610608299327L;

	private TypeCode originalType;

	private JHPType parent;

	private TypeAttributes attr;

	public ComplexType (JHPType parent) throws SemanticException {
		this.parent = parent;
		originalType = parent.getOriginalType();
		attr = new TypeAttributes(parent.getAttributes()); 
	}

	@Override	
	public boolean typeCheck(Object o, TypeCode t) {
		if (!parent.typeCheck(o, t))
			return false;

		switch (originalType) {
		case INTEGER:
			return integerTypeCheck(o);
		case STRING:
			return stringTypeCheck(o);
		case FLOAT:
			return floatTypeCheck(o);
		}

		return false;
	}

	@Override	
	public TypeCode getOriginalType() {
		return originalType;
	}

	@Override	
	public TypeAttributes getAttributes() {
		return attr;
	}

	// INTEGER
	private boolean integerTypeCheck(Object o) {
		try {
			int num = (Integer) o;
			return num >= attr.IntegerMin && num <= attr.IntegerMax;
		} catch (ClassCastException e) {
			return false;
		}
	}

	public void setIntegerMin (int min) throws SemanticException {
		if (!originalType.equals(TypeCode.INTEGER))
			throw new SemanticException("Can't apply min value restriction to this type");

		if (min >= attr.IntegerMin)
			attr.IntegerMin = min;
		else
			throw new SemanticException("Can't restrict min value");
	}

	public void setIntegerMax (int max) throws SemanticException {
		if (!originalType.equals(TypeCode.INTEGER))
			throw new SemanticException("Can't apply max value restriction to this type");

		if (max <= attr.IntegerMax)
			attr.IntegerMax = max;
		else
			throw new SemanticException("Can't restrict max value");
	}

	// FLOAT
	private boolean floatTypeCheck(Object o) {
		try {
			float num = (Float) o;
			return num >= attr.FloatMin && num <= attr.FloatMax;
		} catch (ClassCastException e) {
			return false;
		}
	}

	public void setFloatMin (float min) throws SemanticException {
		if (!originalType.equals(TypeCode.FLOAT))
			throw new SemanticException("Can't apply min value restriction to this type");

		if (min >= attr.FloatMin)
			attr.FloatMin = min;
		else
			throw new SemanticException("Can't restrict min value");
	}

	public void setFloatMax (float max) throws SemanticException {
		if (!originalType.equals(TypeCode.FLOAT))
			throw new SemanticException("Can't apply max value restriction to this type");

		if (max <= attr.FloatMax)
			attr.FloatMax = max;
		else
			throw new SemanticException("Can't restrict max value");
	}

	//STRING
	private boolean stringTypeCheck(Object o) {
		try {
			String s = (String) o;
			if (attr.StringLenMin != null && s.length() < attr.StringLenMin)
				return false;
			if (attr.StringLenMax != null && s.length() > attr.StringLenMax)
				return false;
			if (attr.StringPattern != null && !s.matches(attr.StringPattern))
				return false;
			return true;
		} catch (ClassCastException e) {
			return false;
		}
	}

	public void setStringPattern (String p) {
		attr.StringPattern = p;
	}

	public void setStringLenMin (int min) throws SemanticException {
		if (!originalType.equals(TypeCode.STRING))
			throw new SemanticException("Can't apply min length restriction to this type");

		if (min < attr.StringLenMin)
			throw new SemanticException("Can't restrict min length value");

		if (min > attr.StringLenMax)
			throw new SemanticException("Max string length must be greater than min ");

		attr.StringLenMin = min;
	}

	public void setStringLenMax (int max) throws SemanticException {
		if (!originalType.equals(TypeCode.STRING))
			throw new SemanticException("Can't apply max length restriction to this type");

		if (max > attr.StringLenMax)
			throw new SemanticException("Can't restrict max length value ");

		if (max < attr.StringLenMin)
			throw new SemanticException("Max string length must be greater than min");

		attr.StringLenMax = max;
	}

	public void addAttribute(TypeAttr a, Value[] v) throws SemanticException {
		try {
			switch(originalType) {
			case STRING: 
				switch(a) {				
				case minLength:
					if (!v[0].getType().equals(TypeCode.INTEGER))
						throw new SemanticException("String min length must be Integer");

					int min=(Integer)v[0].getVal();
					setStringLenMin(min);
					break;

				case maxLength:
					if (!v[0].getType().equals(TypeCode.INTEGER))
						throw new SemanticException("String max length must be Integer");

					int max=(Integer)v[0].getVal();
					setStringLenMax(max);
					break;

				case pattern:
					setStringPattern((String)v[0].getVal());
					break;

				default:
					throw new SemanticException("Attribute "+a.name()+" can't be set on a String");

				} break;

			case INTEGER: 
				switch(a) {					
				case range:
					if (!v[0].getType().equals(TypeCode.INTEGER) || !v[1].getType().equals(TypeCode.INTEGER))
						throw new SemanticException("Range type mismatch");

					int i0=(Integer)v[0].getVal();
					int i1=(Integer)v[1].getVal();

					if (i1<i0)
						throw new SemanticException("Range max value must be greater than min value mismatch");

					setIntegerMin(i0);
					setIntegerMax(i1);
					break;
				default:
					throw new SemanticException("Attribute "+a.name()+" can't be set on a Integer");
				} break;

			case FLOAT: 
				switch(a) {
				case range:
					if (!v[0].getType().equals(TypeCode.FLOAT) || !v[1].getType().equals(TypeCode.FLOAT))
						throw new SemanticException("Range type mismatch");

					float f0=(Float)v[0].getVal();
					float f1=(Float)v[1].getVal();

					if (f1<f0)
						throw new SemanticException("Range max value must be greater than min value mismatch");

					setFloatMin(f0);
					setFloatMax(f1);
					break;

				default:
					throw new SemanticException("Attribute "+a.name()+" can't be set on a Float");
				} break;

			}

		} catch (NullPointerException e) {
			throw new SemanticException("Type declaration error ");
		}

	}

	@Override
	public String clientSideCheck(String value) {
		return parent.clientSideCheck(value);
	}


	@Override
	public String toString(){
		StringBuffer res = new StringBuffer();

		res.append(parent.toString());
		String s = attr.toString();
		if (s.length()>0)
			res.append("And (more precisely):\r\n"+s);

		return res.toString();
	}
}
